﻿using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Nvigorate.Data.Query;
using Nvigorate.Relational.Builders;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;
using Nvigorate.Test.Extensions;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational.Repository
{
    [TestClass]
    public class InsertBuilderTests
    {
        #region Fields

        private const string PERSON_VARIABLE_LIST_VARIABLES = "PersonVariableAssignments";
        private const string VEHICLE_VARIABLE_LIST_VARIABLES = "VehicleVariableAssignments";

        #endregion

        #region AssignmentFilter Tests

        [TestMethod]
        public void InsertAssignmentFilter_WhenAssignmentIsPrimaryKey_ReturnsTrue()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            var keyManager = GetKeyManager(false);

            var person = new Person {SocialSecurity = "123456789"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Assert
            Assert.IsTrue(builder.IsInAssignmentFilter(sourceMap.Assignments[0]));
        }

        [TestMethod]
        public void InsertAssignmentFilter_WhenPropertyIsNullAndNullableNotAllowed_ReturnsFalse()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            sourceMap.Assignments[0].Nullable = false;
            var keyManager = GetKeyManager(false);
            var person = new Person {SocialSecurity = null};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Assert
            Assert.IsFalse(builder.IsInAssignmentFilter(sourceMap.Assignments[0]));
        }

        [TestMethod]
        public void InsertAssignmentFilter_WhenPropertyIsNullAndNullableIsAllowed_ReturnsTrue()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            sourceMap.Assignments[0].Nullable = true;
            var keyManager = GetKeyManager(false);
            var person = new Person {SocialSecurity = null};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Assert
            Assert.IsTrue(builder.IsInAssignmentFilter(sourceMap.Assignments[0]));
        }

        [TestMethod]
        public void InsertAssignmentFilter_WhenPropertyIsNotPersistable_ReturnsFalse()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            sourceMap.Assignments[0].Persistable = false;
            var keyManager = GetKeyManager(false);
            var person = new Person {SocialSecurity = "123456789"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Assert
            Assert.IsFalse(builder.IsInAssignmentFilter(sourceMap.Assignments[0]));
        }

        [TestMethod]
        public void InsertAssignmentFilter_ForeignKeyIsNotNullableAndValueIsNull_ReturnsTrue()
        {
            // Arrange
            var sourceMap = GetValidVehicleMap();
            var keyManager = GetKeyManager();
            var vehicle = new Vehicle {VIN = "12345", OwnerSocial = null};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(vehicle), 
                PersistenceDecorator.Create(new Person()));

            // Assert
            Assert.IsTrue(builder.IsInAssignmentFilter(sourceMap.Assignments[0]));
            Assert.IsTrue(builder.IsInAssignmentFilter(sourceMap.Assignments[1]));
        }

        #endregion

        #region Build Tests

        [TestMethod]
        public void Build_WhenNoParentObject_VariableDeclarationsAreInQuery()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            var keyManager = GetKeyManager();
            var person = new Person {SocialSecurity = "123456789"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Act
            var insert = builder.Build(false) as QueryList;

            // Assert
            Assert.IsNotNull(insert);
            Assert.AreEqual(2, insert.Count);
            Assert.AreEqual(PERSON_VARIABLE_LIST_VARIABLES, GetValue(insert[0]));
        }

        [TestMethod]
        public void Build_WhenHasParentObject_VariableDeclarationsAreInQuery()
        {
            // Arrange
            var sourceMap = GetValidVehicleMap();
            var keyManager = GetKeyManager();
            var person = new Person { SocialSecurity = "123456789" };
            var vehicle = new Vehicle {VIN = "12345", OwnerSocial = person.SocialSecurity};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(vehicle), PersistenceDecorator.Create(person));

            // Act
            var insert = builder.Build(false) as QueryList;

            // Assert
            Assert.IsNotNull(insert);
            Assert.AreEqual(2, insert.Count);
            Assert.AreEqual(VEHICLE_VARIABLE_LIST_VARIABLES, GetValue(insert[0]));
        }

        [TestMethod]
        public void Build_WithValidObject_CreatesInsertQueryStructure()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            var keyManager = GetKeyManager();
            var person = new Person {SocialSecurity = "123456789"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Act
            var queryList = builder.Build(false) as QueryList;

            // Assert
            Assert.IsNotNull(queryList);
            var insert = queryList[1] as Insert;
            Assert.AreEqual("Person", insert.Table.Name);
            Assert.AreEqual(1, insert.ColumnList.Count);
            Assert.AreEqual("SSN", ((InsertColumn)insert.ColumnList[0]).Name);
            Assert.AreEqual(1, insert.InsertValues.Count);
            Assert.AreEqual("key1", ((Variable)insert.InsertValues[0]).Name);
        }

        [TestMethod]
        public void Build_PropertyThatDoesNotHaveAKey_WillUseLiteralValue()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            sourceMap.Assignments.Add(new MemberAssignment(false, "Person", "FirstName", "Person", "firstName"));

            var keyManager = GetKeyManager();
            var person = new Person { SocialSecurity = "123456789", FirstName = "John"};
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Act
            var queryList = builder.Build(false) as QueryList;

            // Assert
            Assert.IsNotNull(queryList);
            var insert = queryList[1] as Insert;
            Assert.AreEqual(person.FirstName, ((Literal)insert.InsertValues[1]).Value);            
        }

        [TestMethod]
        public void Build_PropertyThatHasAKey_WillUseVariable()
        {
            // Arrange
            var sourceMap = GetValidPersonMap();
            sourceMap.Assignments.Add(new MemberAssignment(false, "Person", "FirstName", "Person", "firstName"));

            var keyManager = GetKeyManager();
            var person = new Person { SocialSecurity = "123456789", FirstName = "John" };
            var repositoryMapIndex = new Mock<IRepositoryMapIndex>();
            var builder = new InsertBuilderHarness(sourceMap, keyManager, PersistenceDecorator.Create(person), null);

            // Act
            var queryList = builder.Build(false) as QueryList;

            // Assert
            Assert.IsNotNull(queryList);
            var insert = queryList[1] as Insert;
            Assert.AreEqual("key1", ((Variable)insert.InsertValues[0]).Name);                        
        }
        #endregion

        #region Private Methods

        private ISourceMap GetValidPersonMap()
        {
            var sourceMap = new SourceMap("Person") {SourceName = "Person"};
            sourceMap.Assignments.Add(new MemberAssignment(
                                          new AssignmentPoint("Person", "SSN"),
                                          new AssignmentPoint("Person", "SocialSecurity")));
            return sourceMap;
        }

        private ISourceMap GetValidVehicleMap()
        {
            var sourceMap = new SourceMap("Vehicle") {SourceName = "Vehicle"};
            sourceMap.Assignments.Add(new MemberAssignment(true, "Vehicle", "VehicleId", "Vehicle", "VIN"));
            sourceMap.Assignments.Add(new MemberAssignment(false, "Vehicle", "OwnerSSN", "Vehicle", "OwnerSocial"));
            return sourceMap;
        }

        private IPersistenceKeyManager GetKeyManager()
        {
            return GetKeyManager(true);
        }

        private IPersistenceKeyManager GetKeyManager(bool includeExpectations)
        {
            var keyManager = new Mock<IPersistenceKeyManager>();
            if (includeExpectations)
            {
                keyManager.Expect(
                    km => km.GetKeyVariable(typeof(Person), It.Is<IAssignment>(a => a.Source.Field == "SSN"))).
                    Returns("key1");

                keyManager.Expect(
                    km => km.GetVariableAssignments(typeof(Person), It.Is<ISourceMap>(m => m.SourceName == "Person"),
                                                    It.IsAny<IPersistable>())).Returns(ReturnValue(PERSON_VARIABLE_LIST_VARIABLES));

                keyManager.Expect(
                    km => km.GetKeyVariable(typeof(Vehicle), It.Is<IAssignment>(a => a.Source.Field == "OwnerSSN"))).Returns("key2");

                keyManager.Expect(
                    km => km.GetVariableAssignments(typeof(Vehicle), It.Is<ISourceMap>(m => m.SourceName == "Vehicle"),
                                                    It.IsAny<IPersistable>())).Returns(ReturnValue(VEHICLE_VARIABLE_LIST_VARIABLES));
            }

            return keyManager.Object;
        }

        private QueryList ReturnValue(string message)
        {
            return new QueryList().Add(new MessageQueryObject(message));
        }

        private string GetValue(IQueryObject queryObject)
        {
            return ((MessageQueryObject)queryObject).Value;
        }

        #endregion

        #region Nested Types

        private class InsertBuilderHarness : InsertBuilder
        {
            public InsertBuilderHarness(ISourceMap sourceMap, IPersistenceKeyManager keyManager, IPersistable target,
                                        IPersistable parent) : base(sourceMap, keyManager, target, parent)
            {
            }

            
            public bool IsInAssignmentFilter(IAssignment assignment)
            {
                return _assignmentFilter.Invoke(assignment);
            }
        }

        #endregion
    }
}